#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_log.h"
#include "freertos/timers.h"
#include "lvgl_helpers.h"
#include "module_mem.h"
#include "esp_freertos_hooks.h"

static void lv_port_disp_init(void);
static void lv_port_indev_init(void);

static const char* TAG = "module_gui";
static TaskHandle_t s_lv_scheduler_task = NULL;
static SemaphoreHandle_t s_lv_task_sem = NULL;

static void lv_esp_freertos_tick_cb(void)
{
    lv_tick_inc(1);
}

void module_gui_lv_lock(const char* name, int line)
{
    if(NULL != s_lv_task_sem){
        xSemaphoreTake(s_lv_task_sem, portMAX_DELAY);
    }
}

void module_gui_lv_unlock(const char* name, int line)
{
    if(NULL != s_lv_task_sem){
        xSemaphoreGive(s_lv_task_sem);
    }
}

static void lv_scheduler_task(void * arg)
{
    while (1) {
        if(pdTRUE == xSemaphoreTake(s_lv_task_sem, portMAX_DELAY)){
            lv_task_handler();
            xSemaphoreGive(s_lv_task_sem);
        }
        vTaskDelay(5);
    }
}

static void lv_scheduler_task_init()
{
    if(NULL == (s_lv_task_sem = xSemaphoreCreateMutex())){
        ESP_LOGE(TAG, "s_lv_task_sem create fail.");
        return;
    }

    esp_err_t ret = esp_register_freertos_tick_hook_for_cpu(lv_esp_freertos_tick_cb, 1);
    if(ret != ESP_OK){
        ESP_LOGE(TAG, "esp_register_freertos_tick_hook_for_cpu fail.ret=%d", ret);
        return;
    }

    xTaskCreate(lv_scheduler_task, 
                "_lv_scheduler_task", 
                1024 * 5, 
                NULL, 
                tskIDLE_PRIORITY, 
                &s_lv_scheduler_task);
    configASSERT(s_lv_scheduler_task);

    if(s_lv_scheduler_task == NULL) {
        ESP_LOGE(TAG, "_lv_scheduler_task init fail.");
        return;
    }
}

void module_gui_init()
{
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();
    lv_scheduler_task_init();
}

static void lv_port_disp_init(void)
{
    lvgl_driver_init();
    int disp_hor = 240;
    int disp_ver = 280;

    static lv_disp_draw_buf_t draw_buf_dsc_3;
    lv_color_t *buf_3_1 = module_mem_calloc_psram(DISP_BUF_SIZE * sizeof(lv_color_t));            /*A screen sized buffer*/
    lv_color_t *buf_3_2 = module_mem_calloc_psram(DISP_BUF_SIZE * sizeof(lv_color_t));            /*An other screen sized buffer*/
    lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, DISP_BUF_SIZE);                    /*Initialize the display buffer*/

    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/
    disp_drv.hor_res = disp_hor;
    disp_drv.ver_res = disp_ver;
    disp_drv.flush_cb = disp_driver_flush;
    disp_drv.draw_buf = &draw_buf_dsc_3;
    lv_disp_drv_register(&disp_drv);
}

static void lv_port_indev_init(void)
{
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touch_driver_read;
    lv_indev_drv_register(&indev_drv);
}
